Skip to main content

Risk Rules Overview

The framework lets you express risk and execution behaviour as declarative rule lists on a TradingStrategy rather than as ad-hoc code paths. The vector backtest engine, the event-driven backtest engine and the live trading runtime all read the same rule objects, so a strategy behaves identically across all three modes.

from investing_algorithm_framework import (
TradingStrategy,
PositionSize,
StopLossRule,
TakeProfitRule,
ScalingRule,
ExposureRule,
CooldownRule,
)


class MyStrategy(TradingStrategy):
symbols = ["BTC", "ETH"]

position_sizes = [
PositionSize(symbol="BTC", percentage_of_portfolio=20),
PositionSize(symbol="ETH", percentage_of_portfolio=20),
]
stop_losses = [
StopLossRule(
symbol="BTC", percentage_threshold=5,
sell_percentage=100, trailing=True,
),
]
take_profits = [
TakeProfitRule(
symbol="BTC", percentage_threshold=10,
sell_percentage=50, trailing=False,
),
]
scaling_rules = [
ScalingRule(
symbol="BTC", max_entries=3,
scale_in_percentage=[50, 25],
),
]
# Portfolio-wide, unlike the per-symbol lists above: caps total
# invested value across every symbol combined.
exposure_rule = ExposureRule(max_portfolio_percentage=80)
cooldowns = [
CooldownRule(
symbol="BTC", trigger="sell", blocks="buy", bars=12,
),
CooldownRule(trigger="any", blocks="any", bars=2),
]

Fees and slippage (TradingCost) are configured separately — on PortfolioConfiguration/app.add_market(trading_costs=[...]) for live/paper trading, or on Study.execution_config for backtests — since they're a property of the venue/backtest scenario, not of the strategy's signal logic.

The Rule Catalogue​

AttributeClassScopePurpose
position_sizesPositionSizePer-symbolHow much capital to allocate per symbol — fixed amount or percentage of portfolio.
stop_lossesStopLossRulePer-symbolBar-end exit when price drops a fixed or trailing percentage from entry / peak.
take_profitsTakeProfitRulePer-symbolBar-end exit when price rises a fixed or trailing percentage from entry / peak.
scaling_rulesScalingRulePer-symbolPyramid into winners and partially close — max_entries, scale_in_percentage, scale_out_percentage, optional max_position_percentage cap (caps that symbol's position size).
exposure_ruleExposureRulePortfolio-wideCaps total invested value across every symbol combined — e.g. "never more than 80% invested." Singular, not a list.
cooldownsCooldownRulePer-symbol or portfolio-wideSide-aware signal throttling after fills.

TradingCost (fees/slippage) is configured on PortfolioConfiguration/app.add_market(trading_costs=[...]) (live/paper) or Study.execution_config (backtests) — not on the strategy. See Trading Cost.

Position sizing vs. exposure: which one do I need?​

All three can be combined: PositionSize sizes an entry, ScalingRule caps how far one symbol can grow, and ExposureRule is the final portfolio-wide backstop that scales every cash-consuming order down (or drops it) if the combined request would breach the cap.

Where Rules Are Enforced​

RuleVector backtestEvent-driven backtestLive trading
PositionSize✅✅✅
StopLossRule✅✅✅
TakeProfitRule✅✅✅
ScalingRule✅✅✅
ExposureRule✅✅✅
CooldownRule✅✅✅
TradingCost (fees + slippage)✅✅n/a — broker reports actual cost

Resolution Order​

When more than one rule could fire at the same bar, the engine evaluates them in a deterministic order:

  1. Stop loss (highest priority — defensive exit).
  2. Take profit.
  3. CLOSE_LONG signal from generate_signals().
  4. SCALE_OUT signal (only if no full close fired).
  5. OPEN_LONG signal from generate_signals() — gated by CooldownRule and ScalingRule.cooldown_in_bars.
  6. SCALE_IN signal — gated by ScalingRule.max_entries and max_position_percentage.

Within each step, TradingCost is applied to the fill price, and PositionSize (or the relevant scale_in_percentage) determines the order amount. Finally, ExposureRule (if set) scales down or drops any remaining cash-consuming orders so total invested value never exceeds its cap — this runs after sizing, as a portfolio-wide backstop over every symbol's orders for the tick.

See Also​